/*
 * @(#)DefaultDrawingEditor.java
 *
 * Copyright (c) 1996-2010 by the original authors of JHotDraw and all its
 * contributors. All rights reserved.
 *
 * You may not use, copy or modify this file, except in compliance with the 
 * license agreement you entered into with the copyright holders. For details
 * see accompanying license terms.
 */
package org.jhotdraw.draw;

import org.jhotdraw.app.action.edit.PasteAction;
import org.jhotdraw.app.action.edit.CutAction;
import org.jhotdraw.app.action.edit.DeleteAction;
import org.jhotdraw.app.action.edit.CopyAction;
import org.jhotdraw.app.action.edit.SelectAllAction;
import org.jhotdraw.draw.tool.Tool;
import org.jhotdraw.draw.event.ToolEvent;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import org.jhotdraw.beans.*;
import java.awt.*;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.util.*;
import javax.swing.JComponent;
import javax.swing.KeyStroke;
import org.jhotdraw.draw.action.*;
import org.jhotdraw.draw.event.ToolAdapter;
import static org.jhotdraw.draw.AttributeKeys.*;

/**
 * A default implementation of {@link DrawingEditor}.
 * 
 * XXX - DefaultDrawingEditor should not publicly implement ToolListener.
 * 
 * @author Werner Randelshofer
 * @version $Id: DefaultDrawingEditor.java 717 2010-11-21 12:30:57Z rawcoder $
 */
public class DefaultDrawingEditor extends AbstractBean implements DrawingEditor {

	private HashMap<AttributeKey, Object> defaultAttributes = new HashMap<AttributeKey, Object>();
	private HashMap<AttributeKey, Object> handleAttributes = new HashMap<AttributeKey, Object>();
	private Tool tool;
	private HashSet<DrawingView> views;
	private DrawingView activeView;
	private boolean isEnabled = true;
	private ToolHandler toolHandler;

	private class ToolHandler extends ToolAdapter {

		@Override
		public void areaInvalidated(ToolEvent evt) {
			Rectangle r = evt.getInvalidatedArea();
			evt.getView().getComponent().repaint(r.x, r.y, r.width, r.height);
		}

		@Override
		public void toolStarted(ToolEvent evt) {
			setActiveView(evt.getView());
		}

		@Override
		public void boundsInvalidated(ToolEvent evt) {
			Drawing d = evt.getView().getDrawing();
			for (DrawingView v : views) {
				if (v.getDrawing() == d) {
					JComponent c = v.getComponent();
					c.revalidate();
				}
			}
		}
	}

	/**
	 * The input map of the drawing editor.
	 */
	private InputMap inputMap;
	/**
	 * The action map of the drawing editor.
	 */
	private ActionMap actionMap;
	/**
	 * The focus handler.
	 */
	private FocusListener focusHandler = new FocusListener() {

		@Override
		public void focusGained(FocusEvent e) {
			setActiveView((DrawingView) findView((Container) e.getSource()));
		}

		@Override
		public void focusLost(FocusEvent e) {
			/*
			 * if (! e.isTemporary()) { setFocusedView(null); }
			 */
		}
	};

	/** Creates a new instance. */
	public DefaultDrawingEditor() {
		toolHandler = new ToolHandler();
		setDefaultAttribute(FILL_COLOR, Color.white);
		setDefaultAttribute(STROKE_COLOR, Color.black);
		setDefaultAttribute(TEXT_COLOR, Color.black);

		views = new HashSet<DrawingView>();
		inputMap = createInputMap();
		actionMap = createActionMap();
	}

	@Override
	public void setTool(Tool newValue) {
		Tool oldValue = tool;

		if (newValue == tool) {
			return;
		}
		if (tool != null) {
			for (DrawingView v : views) {
				v.removeMouseListener(tool);
				v.removeMouseMotionListener(tool);
				v.removeKeyListener(tool);
			}
			tool.deactivate(this);
			tool.removeToolListener(toolHandler);
		}
		tool = newValue;
		if (tool != null) {
			tool.activate(this);
			for (DrawingView v : views) {
				v.addMouseListener(tool);
				v.addMouseMotionListener(tool);
				v.addKeyListener(tool);
			}
			tool.addToolListener(toolHandler);
		}

		firePropertyChange(TOOL_PROPERTY, oldValue, newValue);
	}

	@Override
	public void setActiveView(DrawingView newValue) {
		DrawingView oldValue = activeView;
		activeView = newValue;

		firePropertyChange(ACTIVE_VIEW_PROPERTY, oldValue, newValue);
	}

	@Override
	public Tool getTool() {
		return tool;
	}

	@Override
	public DrawingView getActiveView() {
		return activeView;
	}

	private void updateActiveView() {
		DrawingView aView = null;
		for (DrawingView v : views) {
			if (v.getComponent().isFocusOwner()) {
				setActiveView(v);
				return;
			}
			aView = v;
		}
		setActiveView(aView);
	}

	@SuppressWarnings("unchecked")
	@Override
	public void applyDefaultAttributesTo(Figure f) {
		for (Map.Entry<AttributeKey, Object> entry : defaultAttributes.entrySet()) {
			f.set(entry.getKey(), entry.getValue());
		}
	}

	@Override
	public <T> T getDefaultAttribute(AttributeKey<T> key) {
		if (defaultAttributes.containsKey(key)) {
			return key.get(defaultAttributes);
		} else {
			return key.getDefaultValue();
		}
	}

	@Override
	public void setDefaultAttribute(AttributeKey key, Object newValue) {
		Object oldValue = defaultAttributes.put(key, newValue);
		firePropertyChange(DEFAULT_ATTRIBUTE_PROPERTY_PREFIX + key.getKey(), oldValue, newValue);
	}

	@Override
	public void remove(DrawingView view) {
		view.getComponent().removeFocusListener(focusHandler);
		views.remove(view);
		if (tool != null) {
			view.removeMouseListener(tool);
			view.removeMouseMotionListener(tool);
			view.removeKeyListener(tool);
		}

		view.removeNotify(this);
		if (activeView == view) {
			view = (views.size() > 0) ? views.iterator().next() : null;
		}
		updateActiveView();
	}

	@Override
	public void add(DrawingView view) {
		views.add(view);
		view.addNotify(this);
		view.getComponent().addFocusListener(focusHandler);
		if (tool != null) {
			view.addMouseListener(tool);
			view.addMouseMotionListener(tool);
			view.addKeyListener(tool);
		}
		updateActiveView();
	}

	@Override
	public void setCursor(Cursor c) {
	}

	@Override
	public Collection<DrawingView> getDrawingViews() {
		return Collections.unmodifiableCollection(views);
	}

	@Override
	public DrawingView findView(Container c) {
		for (DrawingView v : views) {
			if (v.getComponent() == c) {
				return v;
			}
		}
		return null;
	}

	@Override
	public void setEnabled(boolean newValue) {
		if (newValue != isEnabled) {
			boolean oldValue = isEnabled;
			isEnabled = newValue;
			firePropertyChange("enabled", oldValue, newValue);
		}
	}

	@Override
	public boolean isEnabled() {
		return isEnabled;
	}

	@Override
	public Map<AttributeKey, Object> getDefaultAttributes() {
		return Collections.unmodifiableMap(defaultAttributes);
	}

	@Override
	public void setHandleAttribute(AttributeKey key, Object value) {
		handleAttributes.put(key, value);
	}

	@Override
	public <T> T getHandleAttribute(AttributeKey<T> key) {
		if (handleAttributes.containsKey(key)) {
			return key.get(handleAttributes);
		} else {
			return key.getDefaultValue();
		}
	}

	@Override
	public void setInputMap(InputMap newValue) {
		InputMap oldValue = inputMap;
		inputMap = newValue;
		firePropertyChange(INPUT_MAP_PROPERTY, oldValue, newValue);
	}

	@Override
	public InputMap getInputMap() {
		return inputMap;
	}

	@Override
	public void setActionMap(ActionMap newValue) {
		ActionMap oldValue = actionMap;
		actionMap = newValue;
		firePropertyChange(ACTION_MAP_PROPERTY, oldValue, newValue);
	}

	@Override
	public ActionMap getActionMap() {
		return actionMap;
	}

	/**
	 * Override this method to create a tool-specific input map, which overrides
	 * the input map of the drawing edtior.
	 * <p>
	 * The implementation of this class creates an input map for the following
	 * action ID's:
	 * <ul>
	 * <li>DeleteAction</li>
	 * <li>SelectAllAction/li>
	 * <li>IncreaseHandleDetailLevelAction</li>
	 * <li>MoveConstrainedAction.West, .East, .North, .South</li>
	 * <li>MoveAction.West, .East, .North, .South</li>
	 * <li>CutAction</li>
	 * <li>CopyAction</li>
	 * <li>PasteAction</li>
	 * </ul>
	 */
	protected InputMap createInputMap() {
		InputMap m = new InputMap();

		m.put(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0), DeleteAction.ID);
		m.put(KeyStroke.getKeyStroke(KeyEvent.VK_BACK_SPACE, 0), DeleteAction.ID);
		m.put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0), SelectAllAction.ID);
		m.put(KeyStroke.getKeyStroke(KeyEvent.VK_A, InputEvent.CTRL_DOWN_MASK), SelectAllAction.ID);
		m.put(KeyStroke.getKeyStroke(KeyEvent.VK_A, InputEvent.META_DOWN_MASK), SelectAllAction.ID);
		m.put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0), IncreaseHandleDetailLevelAction.ID);

		m.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0), MoveConstrainedAction.West.ID);
		m.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0), MoveConstrainedAction.East.ID);
		m.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0), MoveConstrainedAction.North.ID);
		m.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0), MoveConstrainedAction.South.ID);

		m.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, InputEvent.ALT_DOWN_MASK), MoveAction.West.ID);
		m.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, InputEvent.ALT_DOWN_MASK), MoveAction.East.ID);
		m.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, InputEvent.ALT_DOWN_MASK), MoveAction.North.ID);
		m.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, InputEvent.ALT_DOWN_MASK), MoveAction.South.ID);

		m.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, InputEvent.SHIFT_DOWN_MASK), MoveAction.West.ID);
		m.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, InputEvent.SHIFT_DOWN_MASK), MoveAction.East.ID);
		m.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, InputEvent.SHIFT_DOWN_MASK), MoveAction.North.ID);
		m.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, InputEvent.SHIFT_DOWN_MASK), MoveAction.South.ID);

		m.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, InputEvent.CTRL_DOWN_MASK), MoveAction.West.ID);
		m.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, InputEvent.CTRL_DOWN_MASK), MoveAction.East.ID);
		m.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, InputEvent.CTRL_DOWN_MASK), MoveAction.North.ID);
		m.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, InputEvent.CTRL_DOWN_MASK), MoveAction.South.ID);

		m.put(KeyStroke.getKeyStroke(KeyEvent.VK_C, InputEvent.CTRL_DOWN_MASK), CopyAction.ID);
		m.put(KeyStroke.getKeyStroke(KeyEvent.VK_C, InputEvent.META_DOWN_MASK), CopyAction.ID);
		m.put(KeyStroke.getKeyStroke(KeyEvent.VK_V, InputEvent.CTRL_DOWN_MASK), PasteAction.ID);
		m.put(KeyStroke.getKeyStroke(KeyEvent.VK_V, InputEvent.META_DOWN_MASK), PasteAction.ID);
		m.put(KeyStroke.getKeyStroke(KeyEvent.VK_X, InputEvent.CTRL_DOWN_MASK), CutAction.ID);
		m.put(KeyStroke.getKeyStroke(KeyEvent.VK_X, InputEvent.META_DOWN_MASK), CutAction.ID);

		return m;
	}

	/**
	 * Override this method to create a tool-specific action map, which
	 * overrides the action map of the drawing edtior.
	 * <p>
	 * The implementation of this class creates an action map which maps the
	 * following action ID's to the classes which define them:
	 * <ul>
	 * <li>DeleteAction</li>
	 * <li>SelectAllAction/li>
	 * <li>IncreaseHandleDetailLevelAction</li>
	 * <li>MoveConstrainedAction.West, .East, .North, .South</li>
	 * <li>MoveAction.West, .East, .North, .South</li>
	 * <li>CutAction</li>
	 * <li>CopyAction</li>
	 * <li>PasteAction</li>
	 * </ul>
	 */
	protected ActionMap createActionMap() {
		ActionMap m = new ActionMap();

		m.put(DeleteAction.ID, new DeleteAction());
		m.put(SelectAllAction.ID, new SelectAllAction());
		m.put(IncreaseHandleDetailLevelAction.ID, new IncreaseHandleDetailLevelAction(this));

		m.put(MoveAction.East.ID, new MoveAction.East(this));
		m.put(MoveAction.West.ID, new MoveAction.West(this));
		m.put(MoveAction.North.ID, new MoveAction.North(this));
		m.put(MoveAction.South.ID, new MoveAction.South(this));
		m.put(MoveConstrainedAction.East.ID, new MoveConstrainedAction.East(this));
		m.put(MoveConstrainedAction.West.ID, new MoveConstrainedAction.West(this));
		m.put(MoveConstrainedAction.North.ID, new MoveConstrainedAction.North(this));
		m.put(MoveConstrainedAction.South.ID, new MoveConstrainedAction.South(this));

		m.put(CutAction.ID, new CutAction());
		m.put(CopyAction.ID, new CopyAction());
		m.put(PasteAction.ID, new PasteAction());

		return m;
	}
}
